package main

import (
	// "fmt"
	// "io/ioutil"
	"net/http"
	"log"
	"github.com/gorilla/websocket"
	"strconv"
	"bytes"
	// "encoding/json"
	// "strings"
)

type Client struct {
	sock, stream_sock, play_sock *websocket.Conn
	channel *Channel
	id int
	send chan *map[string] interface{}
	asend chan *bytes.Buffer
	close chan bool
}
type Stream struct {
	client *Client
	stream *bytes.Buffer
}
func newClient(sock *websocket.Conn) *Client {
	return &Client{
		sock: sock,
		stream_sock: nil,
		play_sock: nil,
		send: make(chan *map[string] interface{}),
		asend: make(chan *bytes.Buffer),
		close: make(chan bool),
	}
}
func (c *Client) streamPump(){
	for {
		_, rdr, err := c.stream_sock.NextReader()
		if (err != nil) {
			break
		}
		buf := new(bytes.Buffer)
		buf.ReadFrom(rdr)
		log.Print("get sound: ", len(buf.Bytes()), " <- ", c.sock.RemoteAddr(), c == c.channel.host)
		c.channel.stream <- &Stream{
			client: c,
			stream: buf,
		}
	}
}
func (c *Client) writePump(){
	for {
		select{
		case msg := <- c.send:
			log.Print("send msg: ", msg, " -> ", c.sock.RemoteAddr())
			c.sock.WriteJSON(msg)
		case stream := <- c.asend:
			log.Print("send sound: ", len(stream.Bytes()), " -> ", c.sock.RemoteAddr(), c == c.channel.host)
			if c.play_sock != nil{
				wrt, err := c.play_sock.NextWriter(websocket.BinaryMessage)
				if (err == nil){
					wrt.Write(stream.Bytes())
				}
			}

		case close := <- c.close:
			if close {
				log.Print("cleaning write pump for ", c.sock.RemoteAddr())
				c.sock.Close()
				break
			}
		}
	}
}
func (c *Client) readPump(){
	
	defer func(){
		log.Print("cleaning read for ", c.sock.RemoteAddr())
		c.close <- true
		if (c.channel != nil) {
			if c.channel.host == c{
				c.channel.close <- true
			} else {
				c.channel.unregister <- c
			}
		}
		c.sock.Close()
	} ()
	
	var h *Channel = nil
	msg := make(map[string]interface{})
	err := c.sock.ReadJSON(&msg)
	if err != nil {
		log.Println("read:", err)
		return
	}
	log.Print("got msg: ", c.sock.RemoteAddr(), " -> ", msg)
	if msg["type"] == "channel" {
		name := "channel" + strconv.Itoa(hid)
		hid = hid + 1
		var url string
		url = msg["url"].(string)
		hubs[name] = newChannel(c, url)
		c.send <- &map[string]interface{}{
			"type": "channel_created",
			"name": name,
		}
		
		h = hubs[name]
		c.channel = h
		go h.run()
	} else if msg["type"] == "subscribe" {
		name := msg["name"].(string)
		if channel, ok := hubs[name]; ok {
			//do something her
			channel.register <- c
			c.channel = channel
		} else {
			return
		}
	}
	for {
		msg := make(map[string]interface{})
		err := c.sock.ReadJSON(&msg)
		
		if err != nil {
			log.Println("read:", err)
			break
		}
		log.Print("got msg: ", c.sock.RemoteAddr(), " -> ", msg)
		if msg["type"] == "page" {
			var page int
			page = int(msg["page"].(float64))
			h.page <- page
			h.bcast <- &msg
		} else if msg["type"] == "mousemove" {
			// log.Print("bcast!", h)
			h.bcast <- &msg
		} else if msg["type"] == "viewer-mute" {
			h.viewermute = msg["stat"].(bool)
			h.bcast <- &msg
		}
	}
}

type Channel struct {
	viewers map[*Client] bool
	clients map[int] *Client
	host *Client
	url string
	curpage, curcli int
	viewermute bool
	register chan *Client
	unregister chan *Client
	close chan bool
	bcast chan *map[string]interface{}
	stream chan *Stream
	page chan int
}

func newChannel(host *Client, url string) *Channel {
	return &Channel{
		viewers: make(map[*Client]bool),
		clients: make(map[int]*Client),
		host: host,
		url: url,
		curpage: 1,
		curcli: 0,
		register: make(chan *Client),
		unregister: make(chan *Client),
		bcast: make(chan *map[string]interface{}),
		stream: make(chan *Stream),
		page: make(chan int),
		close: make(chan bool),
	}
}
func (h *Channel) run() {
	for {
		select{
		case client:= <-h.register:
			h.add_client(client)
		case client:= <-h.unregister:
			delete(h.viewers, client)
		case message:= <-h.bcast:
			// log.Print("bcast")
			for client := range h.viewers {
				client.send <- message
			}
		case stream := <-h.stream:
			for client := range h.viewers {
				if (client != stream.client) {
					client.asend <- stream.stream
				}
			}
			if (stream.client != h.host) {
				h.host.asend <- stream.stream
			}
		case page:= <- h.page:
			h.curpage = page
		case close:= <- h.close:
			if close {
				log.Print("closing channel")
				for client := range h.viewers {
					client.close <- true
				}
				break
			}
		}
	}
}
func (h *Channel) add_client(client *Client) {
	h.viewers[client] = true
	client.channel = h
	client.id = h.curcli
	h.curcli += 1
	h.clients[client.id] = client
	// log.Print(h)
	info := map[string]interface{} {
		"type": "channel_info",
		"url": h.url,
		"page": h.curpage,
		"client_id": client.id,
		"mute": h.viewermute,
	}
	client.send <- &info
}

func AnyOrigin(r *http.Request) bool {
	return true
}
var upgrader = websocket.Upgrader{
	CheckOrigin: AnyOrigin,
}

var hubs = map[string]*Channel{}
var hid = 0
func echo(w http.ResponseWriter, r *http.Request){
	c, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print(err)
	}

	client := newClient(c)
	log.Print(c.RemoteAddr(), " connected")
	go client.writePump()
	go client.readPump()
}

func echos(w http.ResponseWriter, r *http.Request) {
	c, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print(err)
	}
	query := r.URL.Query()
	mode := query["mode"][0]
	if mode == "play" {
		client_id, err := strconv.Atoi(query["client_id"][0])
		channel := query["channel"][0]
		
		if err == nil {
			client := hubs[channel].clients[client_id]
			client.play_sock = c
		}
		
	} else if mode == "stream" {
		client_id, err := strconv.Atoi(query["client_id"][0])
		channel := query["channel"][0]
		
		if err == nil {
			client := hubs[channel].clients[client_id]
			client.stream_sock = c
			go client.streamPump()
		}
	} else if mode == "host-stream" {
		channel := query["channel"][0]
		host := hubs[channel].host
		host.stream_sock = c
		go host.streamPump()
	} else if mode == "host-play" {
		channel := query["channel"][0]
		host := hubs[channel].host
		host.play_sock = c
		// go host.streamPump()
	}
	
}
func main() {
	http.HandleFunc("/echo", echo)
	http.HandleFunc("/echos", echos)
	http.Handle("/", http.FileServer(http.Dir(".")))
	http.ListenAndServeTLS("0.0.0.0:8083", "server.crt", "server.key", nil)
	// http.ListenAndServe("0.0.0.0:8080", nil)
}
